/**
 * 
 */
package com.ws.framework.agent;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.ws.framework.ex.UnImplementBeanException;
import com.ws.framework.util.Constants;
import com.ws.framework.util.EncryptUtil;
import com.ws.framework.util.HttpClient;
import com.ws.framework.util.ParamUtil;
import com.ws.framework.util.PropertiesUtil;

import groovy.lang.GroovyClassLoader;

/**
 * <p>
 * </p>
 *
 * @author WSH
 */
public abstract class AbstractBeanFactory {

	private static final Logger logger = LoggerFactory.getLogger(AbstractBeanFactory.class);

	private static final ConcurrentMap<String, Bean> cache = Maps.newConcurrentMap();

	@SuppressWarnings("resource")
	protected Object initObject(String source) {
		Object scriptObj = null;
		try {
			ClassLoader loader = AbstractBeanFactory.class.getClassLoader();
			// 不要采用全局的.PermGen中class能被卸载的前提是ClassLoader中的class没有地方引用
			// 所以公用一个GroovyClassLoader的话很容易导致脚本class无法被卸载
			GroovyClassLoader groovyLoader = new GroovyClassLoader(loader);
			// 每次执行grooovyLoader.parseClass(...),Groovy为了保证每次执行的都是新的脚本内容，在这里每次会生成一个新的class文件
			// 因此对同一个脚本每次都执行次方法时，会导致的现象就是转载的Class越来越多，从而导致PermGen被用满

			/**
			 * 上述两个问题的解决方法就是: 1. 每个script 都 new 一个GroovyClassLoader来装载 2.
			 * 对于parseClass 后新生成的Class对象进行cache, key为groovyScript脚本的md5值
			 */
			Class<?> newClazz = groovyLoader.parseClass(source);
			scriptObj = newClazz.newInstance();
			return scriptObj;
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return null;
	}

	public Bean getBean(String codeName) {
		Bean bean = cache.get(codeName);
		if (!Objects.isNull(bean))
			return bean;
		addWatch(getZkPath(PropertiesUtil.getValue(Constants.APP_CODE)));
		String source = getSource(codeName);
		if (Strings.isNullOrEmpty(source)) {
			logger.warn("根据codeName获取远程源码进行实例化后对象为null, codeName:{}, source:{}", codeName, source);
			return null;
		}
		Object o = initObject(source);
		if (!(o instanceof Bean)) {
			throw new UnImplementBeanException();
		}
		Bean newed = (Bean) o;
		Bean exsit = cache.putIfAbsent(codeName, newed);
		return exsit != null ? exsit : newed;
	}

	protected abstract void addWatch(String path);

	protected String getSource(String codeName) {
		String relativeUrl = PropertiesUtil.getValue(Constants.DYNAMIC_URL);
		// 请求url不能为空
		if (relativeUrl == null || relativeUrl.length() == 0 || !relativeUrl.startsWith("http://")) {
			throw new IllegalArgumentException("Remote source url is error!" + relativeUrl);
		}
		Map<String, Object> data = Maps.newHashMap();
		data.put("codeName", codeName);
		data.put("appCode", PropertiesUtil.getValue(Constants.APP_CODE));
		data.put("timeStamp", System.currentTimeMillis());
		String digestStr = ParamUtil.getParamFromMap(data);
		String mac = EncryptUtil.encryptHMAC(digestStr, PropertiesUtil.getValue(Constants.SECRECTKEY));
		String paramStr = ParamUtil.getEncodedParamFromMap(data);
		StringBuilder paramBuilder = new StringBuilder(paramStr);
		paramBuilder.append("&mac=").append(mac);
		String result = HttpClient.sendByPost(relativeUrl, paramBuilder.toString(), 3000, 3, 1000);
		return result;
	}

	protected void referesh(String codeName) {
		String source = getSource(codeName);
		Object o = initObject(source);
		if (Objects.isNull(o)) {
			logger.warn("根据codeName获取远程源码进行实例化后对象为null, codeName:{}, source:{}", codeName, source);
			return;
		}
		if (!(o instanceof Bean)) {
			throw new UnImplementBeanException();
		}
		cache.put(codeName, (Bean) o);
	}
	
	protected void remove(String codeName) {
		cache.remove(codeName);
	}

	private String getZkPath(String code) {
		return "/app/" + code + "/dynamic";
	}

}
